<!DOCTYPE html>
<html>
  <head>
  <meta http-equiv="content-type" content="text/html; charset=utf-8">
  <meta content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0" name="viewport">
  <meta name="description" content="tong.li&#39;s blog">
  <meta name="keyword" content="彤哥哥博客，95后技术爱好者,现就职于同程旅行/同程艺龙上海分公司，专注于互联网技术分享的平台。">
  
    <link rel="shortcut icon" href="/css/images/icon.png">
  
  <title>
    
      Netty专题-Netty进阶 | 彤哥哥的博客
    
  </title>
  <link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.css" rel="stylesheet">
  <link href="https://cdn.staticfile.org/highlight.js/9.12.0/styles/tomorrow-night.min.css" rel="stylesheet">
  
<link rel="stylesheet" href="/css/style.css">

  
  <script src="https://cdn.staticfile.org/jquery/3.2.1/jquery.min.js"></script>
  <script src="https://cdn.staticfile.org/geopattern/1.2.3/js/geopattern.min.js"></script>
  <script src="https://cdn.staticfile.org/nprogress/0.2.0/nprogress.min.js"></script>
  
    
<script src="/js/qrious.js"></script>

  
  
  
  
    <!-- MathJax support START -->
    <script type="text/x-mathjax-config">
      MathJax.Hub.Config({
        tex2jax: {
          inlineMath: [ ['$','$'], ["\\(","\\)"]  ],
          processEscapes: true,
          skipTags: ['script', 'noscript', 'style', 'textarea', 'pre', 'code']
        }
      });
    </script>

    <script type="text/x-mathjax-config">
      MathJax.Hub.Queue(function() {
        var all = MathJax.Hub.getAllJax(), i;
        for (i=0; i < all.length; i += 1) {
          all[i].SourceElement().parentNode.className += ' has-jax';
        }
      });
    </script>
    <script type="text/javascript" src="https://cdn.staticfile.org/mathjax/2.7.5/MathJax.js?config=TeX-AMS-MML_HTMLorMML"></script>
    <!-- MathJax support END -->
  


  
  
    
<script src="/js/local-search.js"></script>


<meta name="generator" content="Hexo 5.4.2"></head>
<div class="wechat-share">
  <img src="/css/images/logo.png" />
</div>
  <body>
    <header class="header fixed-header">
  <div class="header-container">
    <a class="home-link" href="/">
      <div class="logo"></div>
      <span>彤哥哥的博客</span>
    </a>
    <ul class="right-list">
      
        <li class="list-item">
          
            <a href="/" class="item-link">主页</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/series/" class="item-link">分类</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/tags/" class="item-link">标签</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/archives/" class="item-link">归档</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/project/" class="item-link">项目</a>
          
        </li>
      
        <li class="list-item">
          
            <a href="/about/" class="item-link">关于</a>
          
        </li>
      
      
        <li class="menu-item menu-item-search right-list">
    <a role="button" class="popup-trigger">
        <i class="fa fa-search fa-fw"></i>
    </a>
</li>
      
    </ul>
    <div class="menu">
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
      <span class="icon-bar"></span>
    </div>
    <div class="menu-mask">
      <ul class="menu-list">
        
          <li class="menu-item">
            
              <a href="/" class="menu-link">主页</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/series/" class="menu-link">分类</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/tags/" class="menu-link">标签</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/archives/" class="menu-link">归档</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/project/" class="menu-link">项目</a>
            
          </li>
        
          <li class="menu-item">
            
              <a href="/about/" class="menu-link">关于</a>
            
          </li>
        
      </ul>
    </div>
    
      <div class="search-pop-overlay">
    <div class="popup search-popup">
        <div class="search-header">
            <span class="search-icon">
                <i class="fa fa-search"></i>
            </span>
            <div class="search-input-container">
                <input autocomplete="off" autocapitalize="off"
                    placeholder="Please enter your keyword(s) to search." spellcheck="false"
                    type="search" class="search-input">
            </div>
            <span class="popup-btn-close">
                <i class="fa fa-times-circle"></i>
            </span>
        </div>
        <div id="search-result">
            <div id="no-result">
                <i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>
            </div>
        </div>
    </div>
</div>
    
  </div>
</header>

    <div id="article-banner">
  <h2>Netty专题-Netty进阶</h2>
  <p class="post-date">2021-05-27</p>
  <div class="arrow-down">
    <a href="javascript:;"></a>
  </div>
</div>
<main class="app-body flex-box">
  <!-- Article START -->
  <article class="post-article">
    <section class="markdown-content"><h1 id="1-粘包与半包"><a href="#1-粘包与半包" class="headerlink" title="1. 粘包与半包"></a>1. 粘包与半包</h1><h2 id="1-1-粘包现象"><a href="#1-1-粘包现象" class="headerlink" title="1.1 粘包现象"></a>1.1 粘包现象</h2><p>服务端代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldServer</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldServer.class);</span><br><span class="line">    <span class="keyword">void</span> <span class="title function_">start</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">boss</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>(<span class="number">1</span>);</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">ServerBootstrap</span> <span class="variable">serverBootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>();</span><br><span class="line">            serverBootstrap.channel(NioServerSocketChannel.class);</span><br><span class="line">            serverBootstrap.group(boss, worker);</span><br><span class="line">            serverBootstrap.childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;connected &#123;&#125;&quot;</span>, ctx.channel());</span><br><span class="line">                            <span class="built_in">super</span>.channelActive(ctx);</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelInactive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;disconnect &#123;&#125;&quot;</span>, ctx.channel());</span><br><span class="line">                            <span class="built_in">super</span>.channelInactive(ctx);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> serverBootstrap.bind(<span class="number">8080</span>);</span><br><span class="line">            log.debug(<span class="string">&quot;&#123;&#125; binding...&quot;</span>, channelFuture.channel());</span><br><span class="line">            channelFuture.sync();</span><br><span class="line">            log.debug(<span class="string">&quot;&#123;&#125; bound...&quot;</span>, channelFuture.channel());</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;server error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            boss.shutdownGracefully();</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">            log.debug(<span class="string">&quot;stoped&quot;</span>);</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="keyword">new</span> <span class="title class_">HelloWorldServer</span>().start();</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端代码希望发送 10 个消息，每个消息是 16 字节</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                                buffer.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">14</span>, <span class="number">15</span>&#125;);</span><br><span class="line">                                ctx.writeAndFlush(buffer);</span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;127.0.0.1&quot;</span>, <span class="number">8080</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>服务器端的某次输出，可以看到一次就接收了 160 个字节，而非分 10 次接收</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br></pre></td><td class="code"><pre><span class="line">08:24:46 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0x81e0fda5] binding...</span><br><span class="line">08:24:46 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0x81e0fda5, L:/0:0:0:0:0:0:0:0:8080] bound...</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] REGISTERED</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] ACTIVE</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [<span class="built_in">id</span>: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177]</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] READ: 160B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000010| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000020| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000030| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000040| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000050| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000060| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000070| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000080| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000090| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">08:24:55 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x94132411, L:/127.0.0.1:8080 - R:/127.0.0.1:58177] READ COMPLETE</span><br></pre></td></tr></table></figure>



<h2 id="1-2-半包现象"><a href="#1-2-半包现象" class="headerlink" title="1.2 半包现象"></a>1.2 半包现象</h2><p>客户端代码希望发送 1 个消息，这个消息是 160 字节，代码改为</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line"><span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">    buffer.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">14</span>, <span class="number">15</span>&#125;);</span><br><span class="line">&#125;</span><br><span class="line">ctx.writeAndFlush(buffer);</span><br></pre></td></tr></table></figure>

<p>为现象明显，服务端修改一下接收缓冲区，其它代码不变</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">serverBootstrap.option(ChannelOption.SO_RCVBUF, <span class="number">10</span>);</span><br></pre></td></tr></table></figure>

<p>服务器端的某次输出，可以看到接收的消息被分为两节，第一次 20 字节，第二次 140 字节</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br></pre></td><td class="code"><pre><span class="line">08:43:49 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0x4d6c6a84] binding...</span><br><span class="line">08:43:49 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0x4d6c6a84, L:/0:0:0:0:0:0:0:0:8080] bound...</span><br><span class="line">08:44:23 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] REGISTERED</span><br><span class="line">08:44:23 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] ACTIVE</span><br><span class="line">08:44:23 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221]</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ: 20B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f |................|</span><br><span class="line">|00000010| 00 01 02 03                                     |....            |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ COMPLETE</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ: 140B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000010| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000020| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000030| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000040| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000050| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000060| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000070| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f 00 01 02 03 |................|</span><br><span class="line">|00000080| 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f             |............    |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">08:44:24 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1719abf7, L:/127.0.0.1:8080 - R:/127.0.0.1:59221] READ COMPLETE</span><br></pre></td></tr></table></figure>

<blockquote>
<p><strong>注意</strong></p>
<p>serverBootstrap.option(ChannelOption.SO_RCVBUF, 10) 影响的底层接收缓冲区（即滑动窗口）大小，仅决定了 netty 读取的最小单位，netty 实际每次读取的一般是它的整数倍</p>
</blockquote>
<h2 id="1-3-现象分析"><a href="#1-3-现象分析" class="headerlink" title="1.3 现象分析"></a>1.3 现象分析</h2><p>粘包</p>
<ul>
<li>现象，发送 abc def，接收 abcdef</li>
<li>原因<ul>
<li>应用层：接收方 ByteBuf 设置太大（Netty 默认 1024）</li>
<li>滑动窗口：假设发送方 256 bytes 表示一个完整报文，但由于接收方处理不及时且窗口大小足够大，这 256 bytes 字节就会缓冲在接收方的滑动窗口中，当滑动窗口中缓冲了多个报文就会粘包</li>
<li>Nagle 算法：会造成粘包</li>
</ul>
</li>
</ul>
<p>半包</p>
<ul>
<li>现象，发送 abcdef，接收 abc def</li>
<li>原因<ul>
<li>应用层：接收方 ByteBuf 小于实际发送数据量</li>
<li>滑动窗口：假设接收方的窗口只剩了 128 bytes，发送方的报文大小是 256 bytes，这时放不下了，只能先发送前 128 bytes，等待 ack 后才能发送剩余部分，这就造成了半包</li>
<li>MSS 限制：当发送的数据超过 MSS 限制后，会将数据切分发送，就会造成半包</li>
</ul>
</li>
</ul>
<p>本质是因为 TCP 是流式协议，消息无边界</p>
<blockquote>
<p>滑动窗口</p>
<ul>
<li><p>TCP 以一个段（segment）为单位，每发送一个段就需要进行一次确认应答（ack）处理，但如果这么做，缺点是包的往返时间越长性能就越差</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210806142402.png"></p>
</li>
</ul>
<ul>
<li><p>为了解决此问题，引入了窗口概念，窗口大小即决定了无需等待应答而可以继续发送的数据最大值</p>
<p><img src="https://ltyeamin.github.io/imgs/2021/08/20210806142440.png"></p>
</li>
<li><p>窗口实际就起到一个缓冲区的作用，同时也能起到流量控制的作用</p>
<ul>
<li>图中深色的部分即要发送的数据，高亮的部分即窗口</li>
<li>窗口内的数据才允许被发送，当应答未到达前，窗口必须停止滑动</li>
<li>如果 1001~2000 这个段的数据 ack 回来了，窗口就可以向前滑动</li>
<li>接收方也会维护一个窗口，只有落在窗口内的数据才能允许接收</li>
</ul>
</li>
</ul>
</blockquote>
<blockquote>
<p> MSS 限制</p>
<ul>
<li><p>链路层对一次能够发送的最大数据有限制，这个限制称之为 MTU（maximum transmission unit），不同的链路设备的 MTU 值也有所不同，例如</p>
</li>
<li><p>以太网的 MTU 是 1500</p>
</li>
<li><p>FDDI（光纤分布式数据接口）的 MTU 是 4352</p>
</li>
<li><p>本地回环地址的 MTU 是 65535 - 本地测试不走网卡</p>
</li>
<li><p>MSS 是最大段长度（maximum segment size），它是 MTU 刨去 tcp 头和 ip 头后剩余能够作为数据传输的字节数</p>
</li>
<li><p>ipv4 tcp 头占用 20 bytes，ip 头占用 20 bytes，因此以太网 MSS 的值为 1500 - 40 = 1460</p>
</li>
<li><p>TCP 在传递大量数据时，会按照 MSS 大小将数据进行分割发送</p>
</li>
<li><p>MSS 的值在三次握手时通知对方自己 MSS 的值，然后在两者之间选择一个小值作为 MSS</p>
</li>
</ul>
  <img src="https://ltyeamin.github.io/imgs/2021/08/20210806142550.png" style="zoom:80%;" />
</blockquote>
<blockquote>
<p>Nagle 算法</p>
<ul>
<li>即使发送一个字节，也需要加入 tcp 头和 ip 头，也就是总字节数会使用 41 bytes，非常不经济。因此为了提高网络利用率，tcp 希望尽可能发送足够大的数据，这就是 Nagle 算法产生的缘由</li>
<li>该算法是指发送端即使还有应该发送的数据，但如果这部分数据很少的话，则进行延迟发送<ul>
<li>如果 SO_SNDBUF 的数据达到 MSS，则需要发送</li>
<li>如果 SO_SNDBUF 中含有 FIN（表示需要连接关闭）这时将剩余数据发送，再关闭</li>
<li>如果 TCP_NODELAY = true，则需要发送</li>
<li>已发送的数据都收到 ack 时，则需要发送</li>
<li>上述条件不满足，但发生超时（一般为 200ms）则需要发送</li>
<li>除上述情况，延迟发送</li>
</ul>
</li>
</ul>
</blockquote>
<h2 id="1-4-解决方案"><a href="#1-4-解决方案" class="headerlink" title="1.4 解决方案"></a>1.4 解决方案</h2><ol>
<li>短连接，发一个包建立一次连接，这样连接建立到连接断开之间就是消息的边界，缺点效率太低，并且解决不了半包问题</li>
<li>每一条消息采用固定长度，缺点浪费空间</li>
<li>每一条消息采用分隔符，例如 \n，缺点需要转义</li>
<li>每一条消息分为 head 和 body，head 中包含 body 的长度</li>
</ol>
<h3 id="方法1，短连接"><a href="#方法1，短连接" class="headerlink" title="方法1，短连接"></a>方法1，短连接</h3><p>以解决粘包为例</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="comment">// 分 10 次发送</span></span><br><span class="line">        <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">            send();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">send</span><span class="params">()</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;conneted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            buffer.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">0</span>, <span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>, <span class="number">5</span>, <span class="number">6</span>, <span class="number">7</span>, <span class="number">8</span>, <span class="number">9</span>, <span class="number">10</span>, <span class="number">11</span>, <span class="number">12</span>, <span class="number">13</span>, <span class="number">14</span>, <span class="number">15</span>&#125;);</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                            <span class="comment">// 发完即关</span></span><br><span class="line">                            ctx.close();</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;localhost&quot;</span>, <span class="number">8080</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>输出，略</p>
<blockquote>
<p>半包用这种办法还是不好解决，因为接收方的缓冲区大小是有限的</p>
</blockquote>
<h3 id="方法2，固定长度"><a href="#方法2，固定长度" class="headerlink" title="方法2，固定长度"></a>方法2，固定长度</h3><p>让所有数据包长度固定（假设长度为 8 字节），服务器端加入</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">FixedLengthFrameDecoder</span>(<span class="number">8</span>));</span><br></pre></td></tr></table></figure>

<p>客户端测试代码，注意, 采用这种方法后，客户端什么时候 flush 都可以</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="comment">// 发送内容随机的数据包</span></span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[<span class="number">8</span>];</span><br><span class="line">                                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">0</span>; j &lt; r.nextInt(<span class="number">8</span>); j++) &#123;</span><br><span class="line">                                    bytes[j] = (<span class="type">byte</span>) c;</span><br><span class="line">                                &#125;</span><br><span class="line">                                c++;</span><br><span class="line">                                buffer.writeBytes(bytes);</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;192.168.0.103&quot;</span>, <span class="number">9090</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - connetted...</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x3c2ef3c2] REGISTERED</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x3c2ef3c2] CONNECT: /192.168.0.103:9090</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x3c2ef3c2, L:/192.168.0.103:53155 - R:/192.168.0.103:9090] ACTIVE</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - sending...</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x3c2ef3c2, L:/192.168.0.103:53155 - R:/192.168.0.103:9090] WRITE: 80B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 61 61 61 00 00 00 00 62 00 00 00 00 00 00 00 |aaaa....b.......|</span><br><span class="line">|00000010| 63 63 00 00 00 00 00 00 64 00 00 00 00 00 00 00 |cc......d.......|</span><br><span class="line">|00000020| 00 00 00 00 00 00 00 00 66 66 66 66 00 00 00 00 |........ffff....|</span><br><span class="line">|00000030| 67 67 67 00 00 00 00 00 68 00 00 00 00 00 00 00 |ggg.....h.......|</span><br><span class="line">|00000040| 69 69 69 69 69 00 00 00 6a 6a 6a 6a 00 00 00 00 |iiiii...jjjj....|</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x3c2ef3c2, L:/192.168.0.103:53155 - R:/192.168.0.103:9090] FLUSH</span><br></pre></td></tr></table></figure>

<p>服务端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br></pre></td><td class="code"><pre><span class="line">12:06:51 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0xe3d9713f] binding...</span><br><span class="line">12:06:51 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0xe3d9713f, L:/192.168.0.103:9090] bound...</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] REGISTERED</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] ACTIVE</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155]</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 61 61 61 00 00 00 00                         |aaaa....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 62 00 00 00 00 00 00 00                         |b.......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 63 63 00 00 00 00 00 00                         |cc......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 64 00 00 00 00 00 00 00                         |d.......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 00 00 00 00 00 00 00 00                         |........        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 66 66 66 00 00 00 00                         |ffff....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 67 67 67 00 00 00 00 00                         |ggg.....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 00 00 00 00 00 00 00                         |h.......        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 69 69 69 69 69 00 00 00                         |iiiii...        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6a 6a 6a 6a 00 00 00 00                         |jjjj....        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">12:07:00 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xd739f137, L:/192.168.0.103:9090 - R:/192.168.0.103:53155] READ COMPLETE</span><br></pre></td></tr></table></figure>

<p>缺点是，数据包的大小不好把握</p>
<ul>
<li>长度定的太大，浪费</li>
<li>长度定的太小，对某些数据包又显得不够</li>
</ul>
<h3 id="方法3，固定分隔符"><a href="#方法3，固定分隔符" class="headerlink" title="方法3，固定分隔符"></a>方法3，固定分隔符</h3><p>服务端加入，默认以 \n 或 \r\n 作为分隔符，如果超出指定长度仍未出现分隔符，则抛出异常</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LineBasedFrameDecoder</span>(<span class="number">1024</span>));</span><br></pre></td></tr></table></figure>

<p>客户端在每条消息之后，加入 \n 分隔符</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= r.nextInt(<span class="number">16</span>)+<span class="number">1</span>; j++) &#123;</span><br><span class="line">                                    buffer.writeByte((<span class="type">byte</span>) c);</span><br><span class="line">                                &#125;</span><br><span class="line">                                buffer.writeByte(<span class="number">10</span>);</span><br><span class="line">                                c++;</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;192.168.0.103&quot;</span>, <span class="number">9090</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>客户端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - connetted...</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1282d755] REGISTERED</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1282d755] CONNECT: /192.168.0.103:9090</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1282d755, L:/192.168.0.103:63641 - R:/192.168.0.103:9090] ACTIVE</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - sending...</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1282d755, L:/192.168.0.103:63641 - R:/192.168.0.103:9090] WRITE: 60B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 0a 62 62 62 0a 63 63 63 0a 64 64 0a 65 65 65 |a.bbb.ccc.dd.eee|</span><br><span class="line">|00000010| 65 65 65 65 65 65 65 0a 66 66 0a 67 67 67 67 67 |eeeeeee.ff.ggggg|</span><br><span class="line">|00000020| 67 67 0a 68 68 68 68 0a 69 69 69 69 69 69 69 0a |gg.hhhh.iiiiiii.|</span><br><span class="line">|00000030| 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 0a             |jjjjjjjjjjj.    |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x1282d755, L:/192.168.0.103:63641 - R:/192.168.0.103:9090] FLUSH</span><br></pre></td></tr></table></figure>



<p>服务端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br></pre></td><td class="code"><pre><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] c.i.n.HelloWorldServer - connected [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641]</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 1B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61                                              |a               |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 3B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 62 62 62                                        |bbb             |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 3B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 63 63 63                                        |ccc             |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 64 64                                           |<span class="built_in">dd</span>              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 10B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 65 65 65 65 65 65 65 65 65 65                   |eeeeeeeeee      |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 66                                           |ff              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 7B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 67 67 67 67 67 67 67                            |ggggggg         |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 4B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 68 68 68                                     |hhhh            |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 7B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 69 69 69 69 69 69 69                            |iiiiiii         |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ: 11B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a 6a                |jjjjjjjjjjj     |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:08:18 [DEBUG] [nioEventLoopGroup-3-5] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xa4b3be43, L:/192.168.0.103:9090 - R:/192.168.0.103:63641] READ COMPLETE</span><br></pre></td></tr></table></figure>

<p>缺点，处理字符数据比较合适，但如果内容本身包含了分隔符（字节数据常常会有此情况），那么就会解析错误</p>
<h3 id="方法4，预设长度"><a href="#方法4，预设长度" class="headerlink" title="方法4，预设长度"></a>方法4，预设长度</h3><p>在发送消息前，先约定用定长字节表示接下来数据的长度</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 最大长度，长度偏移，长度占用字节，长度调整，剥离字节数</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LengthFieldBasedFrameDecoder</span>(<span class="number">1024</span>, <span class="number">0</span>, <span class="number">1</span>, <span class="number">0</span>, <span class="number">1</span>));</span><br></pre></td></tr></table></figure>

<p>客户端代码</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">HelloWorldClient</span> &#123;</span><br><span class="line">    <span class="keyword">static</span> <span class="keyword">final</span> <span class="type">Logger</span> <span class="variable">log</span> <span class="operator">=</span> LoggerFactory.getLogger(HelloWorldClient.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="type">Bootstrap</span> <span class="variable">bootstrap</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Bootstrap</span>();</span><br><span class="line">            bootstrap.channel(NioSocketChannel.class);</span><br><span class="line">            bootstrap.group(worker);</span><br><span class="line">            bootstrap.handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                    log.debug(<span class="string">&quot;connetted...&quot;</span>);</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG));</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;sending...&quot;</span>);</span><br><span class="line">                            <span class="type">Random</span> <span class="variable">r</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">Random</span>();</span><br><span class="line">                            <span class="type">char</span> <span class="variable">c</span> <span class="operator">=</span> <span class="string">&#x27;a&#x27;</span>;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">                            <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">i</span> <span class="operator">=</span> <span class="number">0</span>; i &lt; <span class="number">10</span>; i++) &#123;</span><br><span class="line">                                <span class="type">byte</span> <span class="variable">length</span> <span class="operator">=</span> (<span class="type">byte</span>) (r.nextInt(<span class="number">16</span>) + <span class="number">1</span>);</span><br><span class="line">                                <span class="comment">// 先写入长度</span></span><br><span class="line">                                buffer.writeByte(length);</span><br><span class="line">                                <span class="comment">// 再</span></span><br><span class="line">                                <span class="keyword">for</span> (<span class="type">int</span> <span class="variable">j</span> <span class="operator">=</span> <span class="number">1</span>; j &lt;= length; j++) &#123;</span><br><span class="line">                                    buffer.writeByte((<span class="type">byte</span>) c);</span><br><span class="line">                                &#125;</span><br><span class="line">                                c++;</span><br><span class="line">                            &#125;</span><br><span class="line">                            ctx.writeAndFlush(buffer);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;);</span><br><span class="line">            <span class="type">ChannelFuture</span> <span class="variable">channelFuture</span> <span class="operator">=</span> bootstrap.connect(<span class="string">&quot;192.168.0.103&quot;</span>, <span class="number">9090</span>).sync();</span><br><span class="line">            channelFuture.channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<p>客户端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - connetted...</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xf0f347b8] REGISTERED</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xf0f347b8] CONNECT: /192.168.0.103:9090</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xf0f347b8, L:/192.168.0.103:49979 - R:/192.168.0.103:9090] ACTIVE</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] c.i.n.HelloWorldClient - sending...</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xf0f347b8, L:/192.168.0.103:49979 - R:/192.168.0.103:9090] WRITE: 97B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 09 61 61 61 61 61 61 61 61 61 09 62 62 62 62 62 |.aaaaaaaaa.bbbbb|</span><br><span class="line">|00000010| 62 62 62 62 06 63 63 63 63 63 63 08 64 64 64 64 |bbbb.cccccc.dddd|</span><br><span class="line">|00000020| 64 64 64 64 0f 65 65 65 65 65 65 65 65 65 65 65 |dddd.eeeeeeeeeee|</span><br><span class="line">|00000030| 65 65 65 65 0d 66 66 66 66 66 66 66 66 66 66 66 |eeee.fffffffffff|</span><br><span class="line">|00000040| 66 66 02 67 67 02 68 68 0e 69 69 69 69 69 69 69 |ff.gg.hh.iiiiiii|</span><br><span class="line">|00000050| 69 69 69 69 69 69 69 09 6a 6a 6a 6a 6a 6a 6a 6a |iiiiiii.jjjjjjjj|</span><br><span class="line">|00000060| 6a                                              |j               |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-2-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0xf0f347b8, L:/192.168.0.103:49979 - R:/192.168.0.103:9090] FLUSH</span><br></pre></td></tr></table></figure>



<p>服务端输出</p>
<figure class="highlight bash"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br></pre></td><td class="code"><pre><span class="line">14:36:50 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0xdff439d3] binding...</span><br><span class="line">14:36:51 [DEBUG] [main] c.i.n.HelloWorldServer - [<span class="built_in">id</span>: 0xdff439d3, L:/192.168.0.103:9090] bound...</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] REGISTERED</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] ACTIVE</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] c.i.n.HelloWorldServer - connected [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979]</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 9B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 61 61 61 61 61 61 61 61 61                      |aaaaaaaaa       |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 9B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 62 62 62 62 62 62 62 62 62                      |bbbbbbbbb       |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 6B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 63 63 63 63 63 63                               |cccccc          |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 8B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 64 64 64 64 64 64 64 64                         |dddddddd        |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 15B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 65 65 65 65 65 65 65 65 65 65 65 65 65 65 65    |eeeeeeeeeeeeeee |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 13B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 66 66 66 66 66 66 66 66 66 66 66 66 66          |fffffffffffff   |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 67 67                                           |gg              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 2B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 68 68                                           |hh              |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 14B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 69 69 69 69 69 69 69 69 69 69 69 69 69 69       |iiiiiiiiiiiiii  |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ: 9B</span><br><span class="line">         +-------------------------------------------------+</span><br><span class="line">         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">|00000000| 6a 6a 6a 6a 6a 6a 6a 6a 6a                      |jjjjjjjjj       |</span><br><span class="line">+--------+-------------------------------------------------+----------------+</span><br><span class="line">14:37:10 [DEBUG] [nioEventLoopGroup-3-1] i.n.h.l.LoggingHandler - [<span class="built_in">id</span>: 0x744f2b47, L:/192.168.0.103:9090 - R:/192.168.0.103:49979] READ COMPLETE</span><br><span class="line"></span><br></pre></td></tr></table></figure>

<h1 id="2-协议设计与解析"><a href="#2-协议设计与解析" class="headerlink" title="2. 协议设计与解析"></a>2. 协议设计与解析</h1><h2 id="2-1-为什么需要协议？"><a href="#2-1-为什么需要协议？" class="headerlink" title="2.1 为什么需要协议？"></a>2.1 为什么需要协议？</h2><p>TCP/IP 中消息传输基于流的方式，没有边界。</p>
<p>协议的目的就是划定消息的边界，制定通信双方要共同遵守的通信规则</p>
<p>例如：在网络上传输</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下雨天留客天留我不留</span><br></pre></td></tr></table></figure>

<p>是中文一句著名的无标点符号句子，在没有标点符号情况下，这句话有数种拆解方式，而意思却是完全不同，所以常被用作讲述标点符号的重要性</p>
<p>一种解读</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下雨天留客，天留，我不留</span><br></pre></td></tr></table></figure>

<p>另一种解读</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">下雨天，留客天，留我不？留</span><br></pre></td></tr></table></figure>



<p>如何设计协议呢？其实就是给网络传输的信息加上“标点符号”。但通过分隔符来断句不是很好，因为分隔符本身如果用于传输，那么必须加以区分。因此，下面一种协议较为常用</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">定长字节表示内容长度 + 实际内容</span><br></pre></td></tr></table></figure>

<p>例如，假设一个中文字符长度为 3，按照上述协议的规则，发送信息方式如下，就不会被接收方弄错意思了</p>
<figure class="highlight plaintext"><table><tr><td class="gutter"><pre><span class="line">1</span><br></pre></td><td class="code"><pre><span class="line">0f下雨天留客06天留09我不留</span><br></pre></td></tr></table></figure>



<blockquote>
<p>小故事</p>
<p>很久很久以前，一位私塾先生到一家任教。双方签订了一纸协议：“无鸡鸭亦可无鱼肉亦可白菜豆腐不可少不得束修金”。此后，私塾先生虽然认真教课，但主人家则总是给私塾先生以白菜豆腐为菜，丝毫未见鸡鸭鱼肉的款待。私塾先生先是很不解，可是后来也就想通了：主人把鸡鸭鱼肉的钱都会换为束修金的，也罢。至此双方相安无事。</p>
<p>年关将至，一个学年段亦告结束。私塾先生临行时，也不见主人家为他交付束修金，遂与主家理论。然主家亦振振有词：“有协议为证——无鸡鸭亦可，无鱼肉亦可，白菜豆腐不可少，不得束修金。这白纸黑字明摆着的，你有什么要说的呢？”</p>
<p>私塾先生据理力争：“协议是这样的——无鸡，鸭亦可；无鱼，肉亦可；白菜豆腐不可，少不得束修金。”</p>
<p>双方唇枪舌战，你来我往，真个是不亦乐乎！</p>
<p>这里的束修金，也作“束脩”，应当是泛指教师应当得到的报酬</p>
</blockquote>
<h2 id="2-2-redis-协议举例"><a href="#2-2-redis-协议举例" class="headerlink" title="2.2 redis 协议举例"></a>2.2 redis 协议举例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br><span class="line">82</span><br><span class="line">83</span><br><span class="line">84</span><br><span class="line">85</span><br><span class="line">86</span><br><span class="line">87</span><br><span class="line">88</span><br><span class="line">89</span><br><span class="line">90</span><br><span class="line">91</span><br><span class="line">92</span><br><span class="line">93</span><br><span class="line">94</span><br><span class="line">95</span><br><span class="line">96</span><br><span class="line">97</span><br><span class="line">98</span><br><span class="line">99</span><br><span class="line">100</span><br><span class="line">101</span><br><span class="line">102</span><br><span class="line">103</span><br><span class="line">104</span><br><span class="line">105</span><br><span class="line">106</span><br><span class="line">107</span><br><span class="line">108</span><br><span class="line">109</span><br><span class="line">110</span><br><span class="line">111</span><br><span class="line">112</span><br><span class="line">113</span><br><span class="line">114</span><br><span class="line">115</span><br><span class="line">116</span><br><span class="line">117</span><br><span class="line">118</span><br><span class="line">119</span><br><span class="line">120</span><br><span class="line">121</span><br><span class="line">122</span><br><span class="line">123</span><br><span class="line">124</span><br><span class="line">125</span><br><span class="line">126</span><br><span class="line">127</span><br><span class="line">128</span><br><span class="line">129</span><br><span class="line">130</span><br><span class="line">131</span><br><span class="line">132</span><br><span class="line">133</span><br><span class="line">134</span><br><span class="line">135</span><br><span class="line">136</span><br><span class="line">137</span><br><span class="line">138</span><br><span class="line">139</span><br><span class="line">140</span><br><span class="line">141</span><br><span class="line">142</span><br><span class="line">143</span><br><span class="line">144</span><br><span class="line">145</span><br><span class="line">146</span><br><span class="line">147</span><br><span class="line">148</span><br><span class="line">149</span><br><span class="line">150</span><br><span class="line">151</span><br><span class="line">152</span><br><span class="line">153</span><br><span class="line">154</span><br><span class="line">155</span><br><span class="line">156</span><br><span class="line">157</span><br><span class="line">158</span><br><span class="line">159</span><br><span class="line">160</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.netty.bootstrap.Bootstrap;</span><br><span class="line"><span class="keyword">import</span> io.netty.buffer.ByteBuf;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelHandlerContext;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelInboundHandlerAdapter;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelInitializer;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.nio.NioEventLoopGroup;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.socket.SocketChannel;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.socket.nio.NioSocketChannel;</span><br><span class="line"><span class="keyword">import</span> io.netty.handler.codec.redis.RedisMessageType;</span><br><span class="line"><span class="keyword">import</span> io.netty.handler.logging.LoggingHandler;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.StandardCharsets;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.protocol.SimpleRedisClient</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: 基于Netty实现简易版的Redis客户端</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/6/1 10:39</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleRedisClient</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * 回车换行符,13为回车键、10为换行键</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">static</span> <span class="type">byte</span>[] LINE = &#123;<span class="number">13</span>, <span class="number">10</span>&#125;;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Redis通讯协议官方文档：https://redis.io/topics/protocol</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> args</span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> <span class="keyword">throws</span> InterruptedException &#123;</span><br><span class="line">        <span class="comment">// 创建一个工作组</span></span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">worker</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">Bootstrap</span>().group(worker)</span><br><span class="line">                    .channel(NioSocketChannel.class).handler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> &#123;</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>());</span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>()&#123;</span><br><span class="line">                        <span class="comment">/**</span></span><br><span class="line"><span class="comment">                         * 该方法会在连接 channel 建立成功后，会触发 active 事件</span></span><br><span class="line"><span class="comment">                         * <span class="doctag">@param</span> ctx</span></span><br><span class="line"><span class="comment">                         */</span></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelActive</span><span class="params">(ChannelHandlerContext ctx)</span> &#123;</span><br><span class="line">                            setValue(ctx,<span class="string">&quot;name&quot;</span>,<span class="string">&quot;litong&quot;</span>);</span><br><span class="line">                            getValue(ctx,<span class="string">&quot;name&quot;</span>);</span><br><span class="line">                        &#125;</span><br><span class="line"></span><br><span class="line">                        <span class="comment">/**</span></span><br><span class="line"><span class="comment">                         * 读事件处理</span></span><br><span class="line"><span class="comment">                         * <span class="doctag">@param</span> ctx</span></span><br><span class="line"><span class="comment">                         * <span class="doctag">@param</span> msg</span></span><br><span class="line"><span class="comment">                         * <span class="doctag">@throws</span> Exception</span></span><br><span class="line"><span class="comment">                         */</span></span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            <span class="type">ByteBuf</span> <span class="variable">buf</span> <span class="operator">=</span> (ByteBuf) msg;</span><br><span class="line">                            log.info(<span class="string">&quot;返回的内容为:\n&quot;</span> + buf.toString(StandardCharsets.UTF_8));</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).connect(<span class="string">&quot;127.0.0.1&quot;</span>,<span class="number">6379</span>).sync()</span><br><span class="line">                    .channel().closeFuture().sync();</span><br><span class="line"></span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">            log.error(<span class="string">&quot;client error&quot;</span>, e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 优雅关闭</span></span><br><span class="line">            worker.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Redis通讯协议set方法</span></span><br><span class="line"><span class="comment">     * set name litong 解析为如下命令：可以参照io.netty.handler.codec.redis.RedisMessageType类</span></span><br><span class="line"><span class="comment">     * *3\r\n$3\r\nset\r\n$4\r\nname\r\n$6\r\nlitong</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ctx</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> value</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">setValue</span><span class="params">(ChannelHandlerContext ctx, String key, String value)</span> &#123;</span><br><span class="line">        <span class="comment">// 先申请一块足够大的buffer</span></span><br><span class="line">        <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">        <span class="comment">// 往buffer写入*</span></span><br><span class="line">        RedisMessageType.ARRAY_HEADER.writeTo(buffer);</span><br><span class="line">        <span class="comment">// 往buffer写入命令key的个数，set固定为3个命令key:set key value</span></span><br><span class="line">        buffer.writeBytes(<span class="string">&quot;3&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入set命令</span></span><br><span class="line">        RedisMessageType.BULK_STRING.writeTo(buffer);</span><br><span class="line">        buffer.writeBytes(<span class="string">&quot;3&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        buffer.writeBytes(<span class="string">&quot;set&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入Key的key的长度 $</span></span><br><span class="line">        RedisMessageType.BULK_STRING.writeTo(buffer);</span><br><span class="line">        buffer.writeBytes(String.valueOf(key.length()).getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入Key</span></span><br><span class="line">        buffer.writeBytes(key.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入Value的key的长度 $</span></span><br><span class="line">        RedisMessageType.BULK_STRING.writeTo(buffer);</span><br><span class="line">        buffer.writeBytes(String.valueOf(value.length()).getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入Value</span></span><br><span class="line">        buffer.writeBytes(value.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        ctx.writeAndFlush(buffer);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="comment">/**</span></span><br><span class="line"><span class="comment">     * Redis通讯协议get方法</span></span><br><span class="line"><span class="comment">     * set name litong 解析为如下命令：可以参照io.netty.handler.codec.redis.RedisMessageType类</span></span><br><span class="line"><span class="comment">     * *2\r\n$3\r\nget\r\n$4\r\nname\r\n</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> ctx</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@param</span> key</span></span><br><span class="line"><span class="comment">     * <span class="doctag">@return</span></span></span><br><span class="line"><span class="comment">     */</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">getValue</span><span class="params">(ChannelHandlerContext ctx, String key)</span> &#123;</span><br><span class="line">        <span class="comment">// 先申请一块足够大的buffer</span></span><br><span class="line">        <span class="type">ByteBuf</span> <span class="variable">buffer</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">        <span class="comment">// 往buffer写入*</span></span><br><span class="line">        RedisMessageType.ARRAY_HEADER.writeTo(buffer);</span><br><span class="line">        <span class="comment">// 往buffer写入命令key的个数，set固定为3个命令key:set key value</span></span><br><span class="line">        buffer.writeBytes(<span class="string">&quot;2&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入set命令</span></span><br><span class="line">        RedisMessageType.BULK_STRING.writeTo(buffer);</span><br><span class="line">        buffer.writeBytes(<span class="string">&quot;3&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        buffer.writeBytes(<span class="string">&quot;get&quot;</span>.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入Key的key的长度 $</span></span><br><span class="line">        RedisMessageType.BULK_STRING.writeTo(buffer);</span><br><span class="line">        buffer.writeBytes(String.valueOf(key.length()).getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        <span class="comment">// 写入Key</span></span><br><span class="line">        buffer.writeBytes(key.getBytes(StandardCharsets.UTF_8));</span><br><span class="line">        <span class="comment">// 写入换行</span></span><br><span class="line">        buffer.writeBytes(LINE);</span><br><span class="line">        ctx.writeAndFlush(buffer);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="2-3-http-协议举例"><a href="#2-3-http-协议举例" class="headerlink" title="2.3 http 协议举例"></a>2.3 http 协议举例</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br><span class="line">52</span><br><span class="line">53</span><br><span class="line">54</span><br><span class="line">55</span><br><span class="line">56</span><br><span class="line">57</span><br><span class="line">58</span><br><span class="line">59</span><br><span class="line">60</span><br><span class="line">61</span><br><span class="line">62</span><br><span class="line">63</span><br><span class="line">64</span><br><span class="line">65</span><br><span class="line">66</span><br><span class="line">67</span><br><span class="line">68</span><br><span class="line">69</span><br><span class="line">70</span><br><span class="line">71</span><br><span class="line">72</span><br><span class="line">73</span><br><span class="line">74</span><br><span class="line">75</span><br><span class="line">76</span><br><span class="line">77</span><br><span class="line">78</span><br><span class="line">79</span><br><span class="line">80</span><br><span class="line">81</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">import</span> io.netty.bootstrap.ServerBootstrap;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelHandlerContext;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelInboundHandlerAdapter;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.ChannelInitializer;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.SimpleChannelInboundHandler;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.nio.NioEventLoopGroup;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.socket.SocketChannel;</span><br><span class="line"><span class="keyword">import</span> io.netty.channel.socket.nio.NioServerSocketChannel;</span><br><span class="line"><span class="keyword">import</span> io.netty.handler.codec.http.*;</span><br><span class="line"><span class="keyword">import</span> io.netty.handler.logging.LogLevel;</span><br><span class="line"><span class="keyword">import</span> io.netty.handler.logging.LoggingHandler;</span><br><span class="line"><span class="keyword">import</span> lombok.extern.slf4j.Slf4j;</span><br><span class="line"><span class="keyword">import</span> java.nio.charset.StandardCharsets;</span><br><span class="line"></span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@className</span>: com.protocol.SimpleHttpServer</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@description</span>: 简单的HTTP服务器</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@author</span>: tong.li</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@createTime</span>: 2021/6/7 19:32</span></span><br><span class="line"><span class="comment"> * <span class="doctag">@copyright</span>: Tongcheng-Elong Holdings Limited</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">SimpleHttpServer</span> &#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title function_">main</span><span class="params">(String[] args)</span> &#123;</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">boss</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="type">NioEventLoopGroup</span> <span class="variable">workers</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">NioEventLoopGroup</span>();</span><br><span class="line">        <span class="keyword">try</span> &#123;</span><br><span class="line">            <span class="keyword">new</span> <span class="title class_">ServerBootstrap</span>().group(boss,workers).channel(NioServerSocketChannel.class).childHandler(<span class="keyword">new</span> <span class="title class_">ChannelInitializer</span>&lt;SocketChannel&gt;() &#123;</span><br><span class="line">                <span class="meta">@Override</span></span><br><span class="line">                <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">initChannel</span><span class="params">(SocketChannel ch)</span> &#123;</span><br><span class="line">                    <span class="comment">// 添加日志处理器</span></span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(LogLevel.DEBUG))</span><br><span class="line">                            <span class="comment">// 添加HTTP编解码器，由Netty框架提供</span></span><br><span class="line">                            <span class="comment">//该类实现HttpRequestDecoder和HttpResponseEncoder，是全双工的，既可以进行请求解码，也可以进行响应编码</span></span><br><span class="line">                            .addLast(<span class="keyword">new</span> <span class="title class_">HttpServerCodec</span>())</span><br><span class="line">                            <span class="comment">// 添加入站处理器</span></span><br><span class="line">                            .addLast(<span class="keyword">new</span> <span class="title class_">SimpleChannelInboundHandler</span>&lt;HttpRequest&gt;() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">channelRead0</span><span class="params">(ChannelHandlerContext ctx, HttpRequest msg)</span> &#123;</span><br><span class="line">                            <span class="comment">// 获取请求地址</span></span><br><span class="line">                            log.info(<span class="string">&quot;---------请求地址:&quot;</span> + msg.uri() + <span class="string">&quot;------------&quot;</span>);</span><br><span class="line">                            <span class="comment">// 构建响应实体</span></span><br><span class="line">                            <span class="type">DefaultFullHttpResponse</span> <span class="variable">response</span> <span class="operator">=</span></span><br><span class="line">                                    <span class="keyword">new</span> <span class="title class_">DefaultFullHttpResponse</span>(msg.protocolVersion(), HttpResponseStatus.OK);</span><br><span class="line">                            <span class="comment">// 构建响应内容</span></span><br><span class="line">                            <span class="type">byte</span>[] bytes = <span class="string">&quot;&lt;h1 style=\&quot;color:red;\&quot; align=\&quot;center\&quot;&gt;欢迎来到Netty世界!&lt;/h1&gt;&quot;</span>.getBytes(StandardCharsets.UTF_8);</span><br><span class="line">                            <span class="comment">// 设置响应头：Content-Length大小和内容编码</span></span><br><span class="line">                            response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, bytes.length)</span><br><span class="line">                                    .set(HttpHeaderNames.CONTENT_TYPE,</span><br><span class="line">                                    <span class="keyword">new</span> <span class="title class_">StringBuilder</span>(HttpHeaderValues.TEXT_HTML).append(<span class="string">&quot;;charset=&quot;</span>).append(StandardCharsets.UTF_8.name()));</span><br><span class="line">                            <span class="comment">// 响应数据</span></span><br><span class="line">                            response.content().writeBytes(bytes);</span><br><span class="line">                            <span class="comment">// 写回响应</span></span><br><span class="line">                            ctx.writeAndFlush(response);</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line">                    <span class="comment">/* 请求头，请求体处理逻辑 */</span></span><br><span class="line">                    ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelInboundHandlerAdapter</span>() &#123;</span><br><span class="line">                        <span class="meta">@Override</span></span><br><span class="line">                        <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">channelRead</span><span class="params">(ChannelHandlerContext ctx, Object msg)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">                            log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, msg.getClass());</span><br><span class="line">                            <span class="keyword">if</span> (msg <span class="keyword">instanceof</span> HttpRequest) &#123; <span class="comment">// 请求行，请求头</span></span><br><span class="line"></span><br><span class="line">                            &#125; <span class="keyword">else</span> <span class="keyword">if</span> (msg <span class="keyword">instanceof</span> HttpContent) &#123; <span class="comment">//请求体</span></span><br><span class="line"></span><br><span class="line">                            &#125;</span><br><span class="line">                        &#125;</span><br><span class="line">                    &#125;);</span><br><span class="line"></span><br><span class="line">                &#125;</span><br><span class="line">            &#125;).bind(<span class="number">8080</span>).sync().channel().closeFuture().sync();</span><br><span class="line">        &#125; <span class="keyword">catch</span> (InterruptedException e) &#123;</span><br><span class="line">          log.error(<span class="string">&quot;HTTP服务器端异常&quot;</span>,e);</span><br><span class="line">        &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">            <span class="comment">// 优雅关闭</span></span><br><span class="line">            workers.shutdownGracefully();</span><br><span class="line">            boss.shutdownGracefully();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>



<h2 id="2-4-自定义协议要素"><a href="#2-4-自定义协议要素" class="headerlink" title="2.4 自定义协议要素"></a>2.4 自定义协议要素</h2><ul>
<li>魔数，用来在第一时间判定是否是无效数据包</li>
<li>版本号，可以支持协议的升级</li>
<li>序列化算法，消息正文到底采用哪种序列化反序列化方式，可以由此扩展，例如：json、protobuf、hessian、jdk</li>
<li>指令类型，是登录、注册、单聊、群聊… 跟业务相关</li>
<li>请求序号，为了双工通信，提供异步能力</li>
<li>正文长度</li>
<li>消息正文</li>
</ul>
<h3 id="编解码器"><a href="#编解码器" class="headerlink" title="编解码器"></a>编解码器</h3><p>根据上面的要素，设计一个登录请求消息和登录响应消息，并使用 Netty 完成收发</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageCodec</span> <span class="keyword">extends</span> <span class="title class_">ByteToMessageCodec</span>&lt;Message&gt; &#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">encode</span><span class="params">(ChannelHandlerContext ctx, Message msg, ByteBuf out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="comment">// 1. 4 字节的魔数</span></span><br><span class="line">        out.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;);</span><br><span class="line">        <span class="comment">// 2. 1 字节的版本,</span></span><br><span class="line">        out.writeByte(<span class="number">1</span>);</span><br><span class="line">        <span class="comment">// 3. 1 字节的序列化方式 jdk 0 , json 1</span></span><br><span class="line">        out.writeByte(<span class="number">0</span>);</span><br><span class="line">        <span class="comment">// 4. 1 字节的指令类型</span></span><br><span class="line">        out.writeByte(msg.getMessageType());</span><br><span class="line">        <span class="comment">// 5. 4 个字节</span></span><br><span class="line">        out.writeInt(msg.getSequenceId());</span><br><span class="line">        <span class="comment">// 无意义，对齐填充</span></span><br><span class="line">        out.writeByte(<span class="number">0xff</span>);</span><br><span class="line">        <span class="comment">// 6. 获取内容的字节数组</span></span><br><span class="line">        <span class="type">ByteArrayOutputStream</span> <span class="variable">bos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>();</span><br><span class="line">        <span class="type">ObjectOutputStream</span> <span class="variable">oos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(bos);</span><br><span class="line">        oos.writeObject(msg);</span><br><span class="line">        <span class="type">byte</span>[] bytes = bos.toByteArray();</span><br><span class="line">        <span class="comment">// 7. 长度</span></span><br><span class="line">        out.writeInt(bytes.length);</span><br><span class="line">        <span class="comment">// 8. 写入内容</span></span><br><span class="line">        out.writeBytes(bytes);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">decode</span><span class="params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">magicNum</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">version</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">serializerType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">messageType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">sequenceId</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[length];</span><br><span class="line">        in.readBytes(bytes, <span class="number">0</span>, length);</span><br><span class="line">        <span class="type">ObjectInputStream</span> <span class="variable">ois</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(<span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes));</span><br><span class="line">        <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> (Message) ois.readObject();</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;&quot;</span>, magicNum, version, serializerType, messageType, sequenceId, length);</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, message);</span><br><span class="line">        out.add(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="type">EmbeddedChannel</span> <span class="variable">channel</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">EmbeddedChannel</span>(</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">LoggingHandler</span>(),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">LengthFieldBasedFrameDecoder</span>(</span><br><span class="line">        <span class="number">1024</span>, <span class="number">12</span>, <span class="number">4</span>, <span class="number">0</span>, <span class="number">0</span>),</span><br><span class="line">    <span class="keyword">new</span> <span class="title class_">MessageCodec</span>()</span><br><span class="line">);</span><br><span class="line"><span class="comment">// encode</span></span><br><span class="line"><span class="type">LoginRequestMessage</span> <span class="variable">message</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">LoginRequestMessage</span>(<span class="string">&quot;zhangsan&quot;</span>, <span class="string">&quot;123&quot;</span>, <span class="string">&quot;张三&quot;</span>);</span><br><span class="line"><span class="comment">//        channel.writeOutbound(message);</span></span><br><span class="line"><span class="comment">// decode</span></span><br><span class="line"><span class="type">ByteBuf</span> <span class="variable">buf</span> <span class="operator">=</span> ByteBufAllocator.DEFAULT.buffer();</span><br><span class="line"><span class="keyword">new</span> <span class="title class_">MessageCodec</span>().encode(<span class="literal">null</span>, message, buf);</span><br><span class="line"></span><br><span class="line"><span class="type">ByteBuf</span> <span class="variable">s1</span> <span class="operator">=</span> buf.slice(<span class="number">0</span>, <span class="number">100</span>);</span><br><span class="line"><span class="type">ByteBuf</span> <span class="variable">s2</span> <span class="operator">=</span> buf.slice(<span class="number">100</span>, buf.readableBytes() - <span class="number">100</span>);</span><br><span class="line">s1.retain(); <span class="comment">// 引用计数 2</span></span><br><span class="line">channel.writeInbound(s1); <span class="comment">// release 1</span></span><br><span class="line">channel.writeInbound(s2);</span><br></pre></td></tr></table></figure>



<h3 id="💡-什么时候可以加-Sharable"><a href="#💡-什么时候可以加-Sharable" class="headerlink" title="💡 什么时候可以加 @Sharable"></a>💡 什么时候可以加 @Sharable</h3><ul>
<li>当 handler 不保存状态时，就可以安全地在多线程下被共享</li>
<li>但要注意对于编解码器类，不能继承 ByteToMessageCodec 或 CombinedChannelDuplexHandler 父类，他们的构造方法对 @Sharable 有限制</li>
<li>如果能确保编解码器不会保存状态，可以继承 MessageToMessageCodec 父类</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br><span class="line">36</span><br><span class="line">37</span><br><span class="line">38</span><br><span class="line">39</span><br><span class="line">40</span><br><span class="line">41</span><br><span class="line">42</span><br><span class="line">43</span><br><span class="line">44</span><br><span class="line">45</span><br><span class="line">46</span><br><span class="line">47</span><br><span class="line">48</span><br><span class="line">49</span><br><span class="line">50</span><br><span class="line">51</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Slf4j</span></span><br><span class="line"><span class="meta">@ChannelHandler</span>.Sharable</span><br><span class="line"><span class="comment">/**</span></span><br><span class="line"><span class="comment"> * 必须和 LengthFieldBasedFrameDecoder 一起使用，确保接到的 ByteBuf 消息是完整的</span></span><br><span class="line"><span class="comment"> */</span></span><br><span class="line"><span class="keyword">public</span> <span class="keyword">class</span> <span class="title class_">MessageCodecSharable</span> <span class="keyword">extends</span> <span class="title class_">MessageToMessageCodec</span>&lt;ByteBuf, Message&gt; &#123;</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">encode</span><span class="params">(ChannelHandlerContext ctx, Message msg, List&lt;Object&gt; outList)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">ByteBuf</span> <span class="variable">out</span> <span class="operator">=</span> ctx.alloc().buffer();</span><br><span class="line">        <span class="comment">// 1. 4 字节的魔数</span></span><br><span class="line">        out.writeBytes(<span class="keyword">new</span> <span class="title class_">byte</span>[]&#123;<span class="number">1</span>, <span class="number">2</span>, <span class="number">3</span>, <span class="number">4</span>&#125;);</span><br><span class="line">        <span class="comment">// 2. 1 字节的版本,</span></span><br><span class="line">        out.writeByte(<span class="number">1</span>);</span><br><span class="line">        <span class="comment">// 3. 1 字节的序列化方式 jdk 0 , json 1</span></span><br><span class="line">        out.writeByte(<span class="number">0</span>);</span><br><span class="line">        <span class="comment">// 4. 1 字节的指令类型</span></span><br><span class="line">        out.writeByte(msg.getMessageType());</span><br><span class="line">        <span class="comment">// 5. 4 个字节</span></span><br><span class="line">        out.writeInt(msg.getSequenceId());</span><br><span class="line">        <span class="comment">// 无意义，对齐填充</span></span><br><span class="line">        out.writeByte(<span class="number">0xff</span>);</span><br><span class="line">        <span class="comment">// 6. 获取内容的字节数组</span></span><br><span class="line">        <span class="type">ByteArrayOutputStream</span> <span class="variable">bos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ByteArrayOutputStream</span>();</span><br><span class="line">        <span class="type">ObjectOutputStream</span> <span class="variable">oos</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectOutputStream</span>(bos);</span><br><span class="line">        oos.writeObject(msg);</span><br><span class="line">        <span class="type">byte</span>[] bytes = bos.toByteArray();</span><br><span class="line">        <span class="comment">// 7. 长度</span></span><br><span class="line">        out.writeInt(bytes.length);</span><br><span class="line">        <span class="comment">// 8. 写入内容</span></span><br><span class="line">        out.writeBytes(bytes);</span><br><span class="line">        outList.add(out);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">protected</span> <span class="keyword">void</span> <span class="title function_">decode</span><span class="params">(ChannelHandlerContext ctx, ByteBuf in, List&lt;Object&gt; out)</span> <span class="keyword">throws</span> Exception &#123;</span><br><span class="line">        <span class="type">int</span> <span class="variable">magicNum</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">version</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">serializerType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">byte</span> <span class="variable">messageType</span> <span class="operator">=</span> in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">sequenceId</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        in.readByte();</span><br><span class="line">        <span class="type">int</span> <span class="variable">length</span> <span class="operator">=</span> in.readInt();</span><br><span class="line">        <span class="type">byte</span>[] bytes = <span class="keyword">new</span> <span class="title class_">byte</span>[length];</span><br><span class="line">        in.readBytes(bytes, <span class="number">0</span>, length);</span><br><span class="line">        <span class="type">ObjectInputStream</span> <span class="variable">ois</span> <span class="operator">=</span> <span class="keyword">new</span> <span class="title class_">ObjectInputStream</span>(<span class="keyword">new</span> <span class="title class_">ByteArrayInputStream</span>(bytes));</span><br><span class="line">        <span class="type">Message</span> <span class="variable">message</span> <span class="operator">=</span> (Message) ois.readObject();</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;, &#123;&#125;&quot;</span>, magicNum, version, serializerType, messageType, sequenceId, length);</span><br><span class="line">        log.debug(<span class="string">&quot;&#123;&#125;&quot;</span>, message);</span><br><span class="line">        out.add(message);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h1 id="3-空闲检测"><a href="#3-空闲检测" class="headerlink" title="3.  空闲检测"></a>3.  空闲检测</h1><h2 id="连接假死"><a href="#连接假死" class="headerlink" title="连接假死"></a>连接假死</h2><p>原因</p>
<ul>
<li>网络设备出现故障，例如网卡，机房等，底层的 TCP 连接已经断开了，但应用程序没有感知到，仍然占用着资源。</li>
<li>公网网络不稳定，出现丢包。如果连续出现丢包，这时现象就是客户端数据发不出去，服务端也一直收不到数据，就这么一直耗着</li>
<li>应用程序线程阻塞，无法进行数据读写</li>
</ul>
<p>问题</p>
<ul>
<li>假死的连接占用的资源不能自动释放</li>
<li>向假死的连接发送数据，得到的反馈是发送超时</li>
</ul>
<p>服务器端解决</p>
<ul>
<li>怎么判断客户端连接是否假死呢？如果能收到客户端数据，说明没有假死。因此策略就可以定为，每隔一段时间就检查这段时间内是否接收到客户端数据，没有就可以判定为连接假死</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用来判断是不是 读空闲时间过长，或 写空闲时间过长</span></span><br><span class="line"><span class="comment">// 5s 内如果没有收到 channel 的数据，会触发一个 IdleState#READER_IDLE 事件</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">IdleStateHandler</span>(<span class="number">5</span>, <span class="number">0</span>, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// ChannelDuplexHandler 可以同时作为入站和出站处理器</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelDuplexHandler</span>() &#123;</span><br><span class="line">    <span class="comment">// 用来触发特殊事件</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">IdleStateEvent</span> <span class="variable">event</span> <span class="operator">=</span> (IdleStateEvent) evt;</span><br><span class="line">        <span class="comment">// 触发了读空闲事件</span></span><br><span class="line">        <span class="keyword">if</span> (event.state() == IdleState.READER_IDLE) &#123;</span><br><span class="line">            log.debug(<span class="string">&quot;已经 5s 没有读到数据了&quot;</span>);</span><br><span class="line">            ctx.channel().close();</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>



<p>客户端定时心跳</p>
<ul>
<li>客户端可以定时向服务器端发送数据，只要这个时间间隔小于服务器定义的空闲检测的时间间隔，那么就能防止前面提到的误判，客户端可以定义如下心跳处理器</li>
</ul>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br></pre></td><td class="code"><pre><span class="line"><span class="comment">// 用来判断是不是 读空闲时间过长，或 写空闲时间过长</span></span><br><span class="line"><span class="comment">// 3s 内如果没有向服务器写数据，会触发一个 IdleState#WRITER_IDLE 事件</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">IdleStateHandler</span>(<span class="number">0</span>, <span class="number">3</span>, <span class="number">0</span>));</span><br><span class="line"><span class="comment">// ChannelDuplexHandler 可以同时作为入站和出站处理器</span></span><br><span class="line">ch.pipeline().addLast(<span class="keyword">new</span> <span class="title class_">ChannelDuplexHandler</span>() &#123;</span><br><span class="line">    <span class="comment">// 用来触发特殊事件</span></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">void</span> <span class="title function_">userEventTriggered</span><span class="params">(ChannelHandlerContext ctx, Object evt)</span> <span class="keyword">throws</span> Exception&#123;</span><br><span class="line">        <span class="type">IdleStateEvent</span> <span class="variable">event</span> <span class="operator">=</span> (IdleStateEvent) evt;</span><br><span class="line">        <span class="comment">// 触发了写空闲事件</span></span><br><span class="line">        <span class="keyword">if</span> (event.state() == IdleState.WRITER_IDLE) &#123;</span><br><span class="line">            <span class="comment">//                                log.debug(&quot;3s 没有写数据了，发送一个心跳包&quot;);</span></span><br><span class="line">            ctx.writeAndFlush(<span class="keyword">new</span> <span class="title class_">PingMessage</span>());</span><br><span class="line">        &#125;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;);</span><br></pre></td></tr></table></figure>





</section>
    <!-- Tags START -->
    
      <div class="tags">
        <span>Tags:</span>
        
  <a href="/tags#NIO" >
    <span class="tag-code">NIO</span>
  </a>

      </div>
    
    <!-- Tags END -->
    <!-- NAV START -->
    
  <div class="nav-container">
    <!-- reverse left and right to put prev and next in a more logic postition -->
    
      <a class="nav-left" href="/2021/05/26/Netty%E4%B8%93%E9%A2%98-Netty%E5%85%A5%E9%97%A8/">
        <span class="nav-arrow">← </span>
        
          Netty专题-Netty入门
        
      </a>
    
    
      <a class="nav-right" href="/2021/08/01/Linux%E8%BF%90%E7%BB%B4-%E5%AE%9E%E7%94%A8%E7%9A%84%E5%B0%8F%E8%84%9A%E6%9C%AC/">
        
          Linux运维-实用的小脚本
        
        <span class="nav-arrow"> →</span>
      </a>
    
  </div>

    <!-- NAV END -->
    <!-- 打赏 START -->
    
      <div class="money-like">
        <div class="reward-btn">
          赏
          <span class="money-code">
            <span class="alipay-code">
              <div class="code-image"></div>
              <b>使用支付宝打赏</b>
            </span>
            <span class="wechat-code">
              <div class="code-image"></div>
              <b>使用微信打赏</b>
            </span>
          </span>
        </div>
        <p class="notice">若你觉得我的文章对你有帮助，欢迎点击上方按钮对我打赏</p>
      </div>
    
    <!-- 打赏 END -->
    <!-- 二维码 START -->
    
      <div class="qrcode">
        <canvas id="share-qrcode"></canvas>
        <p class="notice">扫描二维码，分享此文章</p>
      </div>
    
    <!-- 二维码 END -->
    
      <!-- Utterances START -->
      <div id="utterances"></div>
      <script src="https://utteranc.es/client.js"
        repo="ltyeamin/blogtalks"
        issue-term="pathname"
        theme="github-light"
        crossorigin="anonymous"
        async></script>    
      <!-- Utterances END -->
    
  </article>
  <!-- Article END -->
  <!-- Catalog START -->
  
    <aside class="catalog-container">
  <div class="toc-main">
    <strong class="toc-title">Catalog</strong>
    
      <ol class="toc-nav"><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#1-%E7%B2%98%E5%8C%85%E4%B8%8E%E5%8D%8A%E5%8C%85"><span class="toc-nav-text">1. 粘包与半包</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#1-1-%E7%B2%98%E5%8C%85%E7%8E%B0%E8%B1%A1"><span class="toc-nav-text">1.1 粘包现象</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#1-2-%E5%8D%8A%E5%8C%85%E7%8E%B0%E8%B1%A1"><span class="toc-nav-text">1.2 半包现象</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#1-3-%E7%8E%B0%E8%B1%A1%E5%88%86%E6%9E%90"><span class="toc-nav-text">1.3 现象分析</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#1-4-%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88"><span class="toc-nav-text">1.4 解决方案</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%96%B9%E6%B3%951%EF%BC%8C%E7%9F%AD%E8%BF%9E%E6%8E%A5"><span class="toc-nav-text">方法1，短连接</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%96%B9%E6%B3%952%EF%BC%8C%E5%9B%BA%E5%AE%9A%E9%95%BF%E5%BA%A6"><span class="toc-nav-text">方法2，固定长度</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%96%B9%E6%B3%953%EF%BC%8C%E5%9B%BA%E5%AE%9A%E5%88%86%E9%9A%94%E7%AC%A6"><span class="toc-nav-text">方法3，固定分隔符</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E6%96%B9%E6%B3%954%EF%BC%8C%E9%A2%84%E8%AE%BE%E9%95%BF%E5%BA%A6"><span class="toc-nav-text">方法4，预设长度</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#2-%E5%8D%8F%E8%AE%AE%E8%AE%BE%E8%AE%A1%E4%B8%8E%E8%A7%A3%E6%9E%90"><span class="toc-nav-text">2. 协议设计与解析</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-1-%E4%B8%BA%E4%BB%80%E4%B9%88%E9%9C%80%E8%A6%81%E5%8D%8F%E8%AE%AE%EF%BC%9F"><span class="toc-nav-text">2.1 为什么需要协议？</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-2-redis-%E5%8D%8F%E8%AE%AE%E4%B8%BE%E4%BE%8B"><span class="toc-nav-text">2.2 redis 协议举例</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-3-http-%E5%8D%8F%E8%AE%AE%E4%B8%BE%E4%BE%8B"><span class="toc-nav-text">2.3 http 协议举例</span></a></li><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#2-4-%E8%87%AA%E5%AE%9A%E4%B9%89%E5%8D%8F%E8%AE%AE%E8%A6%81%E7%B4%A0"><span class="toc-nav-text">2.4 自定义协议要素</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%E7%BC%96%E8%A7%A3%E7%A0%81%E5%99%A8"><span class="toc-nav-text">编解码器</span></a></li><li class="toc-nav-item toc-nav-level-3"><a class="toc-nav-link" href="#%F0%9F%92%A1-%E4%BB%80%E4%B9%88%E6%97%B6%E5%80%99%E5%8F%AF%E4%BB%A5%E5%8A%A0-Sharable"><span class="toc-nav-text">💡 什么时候可以加 @Sharable</span></a></li></ol></li></ol></li><li class="toc-nav-item toc-nav-level-1"><a class="toc-nav-link" href="#3-%E7%A9%BA%E9%97%B2%E6%A3%80%E6%B5%8B"><span class="toc-nav-text">3.  空闲检测</span></a><ol class="toc-nav-child"><li class="toc-nav-item toc-nav-level-2"><a class="toc-nav-link" href="#%E8%BF%9E%E6%8E%A5%E5%81%87%E6%AD%BB"><span class="toc-nav-text">连接假死</span></a></li></ol></li></ol>
    
  </div>
</aside>
  
  <!-- Catalog END -->
</main>

<script>
  (function () {
    var url = 'http://example.com/2021/05/27/Netty专题-Netty进阶/';
    var banner = ''
    if (banner !== '' && banner !== 'undefined' && banner !== 'null') {
      $('#article-banner').css({
        'background-image': 'url(' + banner + ')'
      })
    } else {
      $('#article-banner').geopattern(url)
    }
    $('.header').removeClass('fixed-header')

    // error image
    $(".markdown-content img").on('error', function() {
      $(this).attr('src', '/css/images/error_icon.png')
      $(this).css({
        'cursor': 'default'
      })
    })

    // zoom image
    $(".markdown-content img").on('click', function() {
      var src = $(this).attr('src')
      if (src !== '/css/images/error_icon.png') {
        var imageW = $(this).width()
        var imageH = $(this).height()

        var zoom = ($(window).width() * 0.95 / imageW).toFixed(2)
        zoom = zoom < 1 ? 1 : zoom
        zoom = zoom > 2 ? 2 : zoom
        var transY = (($(window).height() - imageH) / 2).toFixed(2)

        $('body').append('<div class="image-view-wrap"><div class="image-view-inner"><img src="'+ src +'" /></div></div>')
        $('.image-view-wrap').addClass('wrap-active')
        $('.image-view-wrap img').css({
          'width': `${imageW}`,
          'transform': `translate3d(0, ${transY}px, 0) scale3d(${zoom}, ${zoom}, 1)`
        })
        $('html').css('overflow', 'hidden')

        $('.image-view-wrap').on('click', function() {
          $(this).remove()
          $('html').attr('style', '')
        })
      }
    })
  })();
</script>


  <script>
    var qr = new QRious({
      element: document.getElementById('share-qrcode'),
      value: document.location.href
    });
  </script>






    <div class="scroll-top">
  <span class="arrow-icon"></span>
</div>
    <footer class="app-footer">
  <p class="copyright">
    &copy; 2024 | Proudly powered by <a href="https://hexo.io" target="_blank">Hexo</a>
    <br>
    Theme by <a target="_blank" rel="noopener" href="https://github.com/ltyeamin">tong.li</a>
  </p>
</footer>

<script>
  function async(u, c) {
    var d = document, t = 'script',
      o = d.createElement(t),
      s = d.getElementsByTagName(t)[0];
    o.src = u;
    if (c) { o.addEventListener('load', function (e) { c(null, e); }, false); }
    s.parentNode.insertBefore(o, s);
  }
</script>
<script>
  async("https://cdn.staticfile.org/fastclick/1.0.6/fastclick.min.js", function(){
    FastClick.attach(document.body);
  })
</script>

<script>
  var hasLine = 'true';
  async("https://cdn.staticfile.org/highlight.js/9.12.0/highlight.min.js", function(){
    $('figure pre').each(function(i, block) {
      var figure = $(this).parents('figure');
      if (hasLine === 'false') {
        figure.find('.gutter').hide();
      }
      hljs.configure({useBR: true});
      var lang = figure.attr('class').split(' ')[1] || 'code';
      var codeHtml = $(this).html();
      var codeTag = document.createElement('code');
      codeTag.className = lang;
      codeTag.innerHTML = codeHtml;
      $(this).attr('class', '').empty().html(codeTag);
      figure.attr('data-lang', lang.toUpperCase());
      hljs.highlightBlock(block);
    });
  })
</script>
<!-- Baidu Tongji -->



<script src='https://cdn.staticfile.org/mermaid/8.11.2/mermaid.min.js'></script>



<script src="/js/script.js"></script>


  </body>
</html>